// (c) S.A. Velastin 2007

#include <iostream>
#include <fstream>
#include <string>

// Corba stuff
#include "orbsvcs/CosNamingC.h"
#include "CommunicationC.h"

// Opencv stuff
#include <cv.h>
#include <highgui.h>


using namespace std;

//--------------------------------------------------------------------------------------------
// Gets a reference to the Name Service
CosNaming::NamingContext_var GetNamingContext (CORBA::ORB_var orb) {
  CosNaming::NamingContext_var naming_context = NULL;
  cout << "Trying to locate the Naming Service\n";
  try {
    CORBA::Object_var naming_context_object = orb->resolve_initial_references ("NameService");
    naming_context = CosNaming::NamingContext::_narrow (naming_context_object.in ());
    if (CORBA::is_nil(naming_context))
      cout << "Could not resolve Name Service\n";
    else
      cout << "Got Name Service\n";
  }
  catch (CORBA::Exception& e) {
    cerr << "Exception when getting Name Service: " << e << endl;
  }
  return naming_context;
}

//--------------------------------------------------------------------------------------------
// Gets a reference to an IDA Factory via Naming Service
TIDAFactory_var GetIDAFactory (char *FactoryName, CosNaming::NamingContext_var naming_context) {
  TIDAFactory_var Factory = NULL;
  cout << "Trying to get reference to factory object '" << FactoryName << "'\n";
  // Create and initialise the name sequence
  CosNaming::Name name (1);
  name.length(1);
  // Initialise the name
  name[0].id = CORBA::string_dup (FactoryName);

  try {
    CORBA::Object_var obj = naming_context->resolve (name);
    if (CORBA::is_nil(obj)) 
      cerr << "Unable to resolve name\n";
    else {
        // Get a reference to the Factory object
      Factory = TIDAFactory::_narrow(obj);
      if (CORBA::is_nil(Factory))
        cerr << "Unable to get Factory reference\n";
      return Factory;
    }
  }
  catch (CORBA::Exception& e) {
    cerr << "Exception when getting factory: " << e << endl;
  }
  return Factory;
}

//--------------------------------------------------------------------------------------------
// Gets a reference to an object by getting it from the Name Service
TIDA_var GetIDAReference (char *objname, CosNaming::NamingContext_var naming_context) {
  TIDA_var result=NULL;
  CosNaming::Name name (1);
  name.length(1);
  name[0].id = CORBA::string_dup (objname);
  cout << "Client looking for IDA name '" << objname << "' in name service\n";
  try {
    CORBA::Object_var obj = naming_context->resolve (name);
    if (CORBA::is_nil(obj)) {
    cerr << "Unable to resolve name\n";
    return TIDA::_nil();
    }
    // The name was resolved, try to use the object
    result = TIDA::_narrow (obj);
    if (CORBA::is_nil(result))
      cerr << "Unable to get the Factory's object reference\n";
  }
  catch (CORBA::Exception &e) {
    cerr << "Exception when getting IDA object reference: " << e << endl;
  }
  cout << "Object has been found\n";
  return result;  
}

//------------------------------------------------------------------------------
// Fills up an IplImage from a TImage
// Assumed that Image owns its pixel data so we create a new array to be owned by CVImage
// NB: Caller has responsibility for freeing memory in CVImage
bool FillOpenCVData (IplImage *CVImage, const TImage *Image)
{ if (Image->imageSize != Image->ImagePixels.length()) {
  cerr << "FillOpenCVData: inconsistent image size " << Image->imageSize << " and length " 
      << Image->ImagePixels.length() << "\n";
  return false;
}
  
  CVImage->nSize = sizeof(IplImage);
  CVImage->ID = 0;                      // version (=0)
  CVImage->nChannels = Image->nChannels;// Most of OpenCV functions support 1,2,3 or 4 channels 
  CVImage->alphaChannel = 0;            // ignored by OpenCV 
  CVImage->depth = Image->depth;        // pixel depth in bits: IPL_DEPTH_8U, IPL_DEPTH_8S, IPL_DEPTH_16U,
                                        //IPL_DEPTH_16S, IPL_DEPTH_32S, IPL_DEPTH_32F and IPL_DEPTH_64F are supported
  //    char colorModel[4];           ignored by OpenCV 
  //    char channelSeq[4];           ditto 
  CVImage->dataOrder = 0;               // 0 - interleaved color channels, 1 - separate color channels.
                                        // cvCreateImage can only create interleaved images 
  CVImage->origin = Image->origin;      // 0 - top-left origin,
                                        // 1 - bottom-left origin (Windows bitmaps style) 
  // int  align;                           Alignment of image rows (4 or 8).
                                        // OpenCV ignores it and uses widthStep instead 
  CVImage->width = Image->width;        // image width in pixels 
  CVImage->height = Image->height;      // image height in pixels 
  CVImage->roi = NULL;                  // image ROI. when it is not NULL, this specifies image region to process 
  CVImage->maskROI = NULL;              // must be NULL in OpenCV 
  CVImage->imageId = NULL;              // ditto
  CVImage->tileInfo = NULL;             // ditto
  CVImage->imageSize = Image->imageSize;// image data size in bytes
                                        // (=image->height*image->widthStep
                                        //  in case of interleaved data)
  CVImage->widthStep = Image->widthStep;// size of aligned image row in bytes
  //       int  BorderMode[4];           border completion mode, ignored by OpenCV 
  //       int  BorderConst[4];          ditto
  char *Pixels = new (nothrow) char[Image->imageSize];
  if (!Pixels) {
    cerr << "FillOpenCVData: could not create pixels array\n";
    return false;
  }
  memcpy (Pixels, Image->ImagePixels.get_buffer(), Image->imageSize);
  
  CVImage->imageData = Pixels;          // pointer to aligned image data
  CVImage->imageDataOrigin = Pixels;    // pointer to a very origin of image data
                                        // (not necessarily aligned) -
                                        // it is needed for correct image deallocation
  // note that the caller is now responsible for releasing Pixels
  return true;
}

//----------------------------------------------------------------------------------
// Fills a TImage from IplImage
// A new array of pixels is created that is then managed by Image
bool FillImageData (TImage *Image, const IplImage *CVImage)
{ Image->nChannels = CVImage->nChannels;
  Image->depth = CVImage->depth;
  Image->origin = CVImage->origin;
  Image->width = CVImage->width;
  Image->height = CVImage->height;
  Image->imageSize = (CORBA::Long) CVImage->imageSize;
  Image->widthStep = CVImage->widthStep;
  // Now copy the pixel data by creating memory and copying
  CORBA::Octet *ImageData;
  ImageData = new (nothrow) CORBA::Octet[CVImage->imageSize];
  if (!ImageData) {
    cerr << "FillImageData failed to create temporary memory\n";
    return false;
  }
  memcpy (ImageData, CVImage->imageDataOrigin, CVImage->imageSize);  // copy all pixels
  
  // Important: Image now manages ImageData, so do NOT delete it
  Image->ImagePixels.replace (CVImage->imageSize, CVImage->imageSize, ImageData);
  cout << "Pixel data copied, size: " << CVImage->imageSize << " " << Image->imageSize << 
      " " << Image->ImagePixels.length() << "\n";
  return true;
}

// Possible exit codes
#define EXIT_NORMAL             0
#define EXIT_WRONGPARAMETERS    1
#define EXIT_NONAMINGSERVICE    2
#define EXIT_NOFACTORY          3
#define EXIT_NOIMAGEMEMORY      4
#define EXIT_NOIMAGEFILE        5
#define EXIT_COULDNOTCOPY       6 

//--------------------------------------------------------------------------------------------
int main(int argc, char* argv[]) { 
  char message[100], *filename;

  try { 
    CosNaming::NamingContext_var naming_context;
    TIDAFactory_var Factory;                    // Reference to an IDA Factory
    TIDA_var IDA_obj;                           // Reference to an IDA object
    IplImage *CVimage;                          // To hold an image (OpenCV)
    TImage Image;                               // To hold an image (us)
    CORBA::Any SentImage, ReadImage;            // What is sent or received from IDAs
        
    // Process & remove any CORBA arguments in the command line
    CORBA::ORB_var orb = CORBA::ORB_init(argc, argv);
    // Check command line arguments.
    if (argc < 2) { 
      cerr << "Usage: Client <object_factory> [<picture_filename>]\n";
      exit (EXIT_WRONGPARAMETERS); }
    
    // Get a reference to the naming service
    naming_context = GetNamingContext (orb);
    if (CORBA::is_nil(naming_context))
      exit (EXIT_NONAMINGSERVICE);
    
    // Get a reference to the factory object
    Factory = GetIDAFactory (argv[1], naming_context);
    if (CORBA::is_nil (Factory))  
      exit (EXIT_NOFACTORY);
    
    // Here we read the image file
    filename = argc == 3 ? argv[2] : (char*)"fruits.jpg";
    cout << "Filename is '" << filename <<"'\n";
    if( (CVimage = cvLoadImage( filename, 1)) == 0 ) {    // Get pointer to an image
      cerr << "Failed to open the file\n";
      exit (EXIT_NOIMAGEFILE); }
    cvNamedWindow("Video", CV_WINDOW_AUTOSIZE);
    
    cout << "Image size is " << CVimage->imageSize << "\n";
    
    // And now we transfer data to TImage
    if (!FillImageData (&Image, CVimage)) {
      cerr << "Unable to copy image to a TImage\n";
      exit (EXIT_COULDNOTCOPY);
    }
      
    SentImage <<= Image;                        // Copy Image to SentImage
    cout << "Ready to create some objects\n";
    
    // Now we are ready for a few IDA object tests
    // This demonstrates creation/removal of objects
    if (Factory->CreateObject ("One", IDAflash, SentImage)) {
      cout << "Flash element created, now we will get some data\n";
      IDA_obj = GetIDAReference ("One", naming_context);
      if (CORBA::is_nil(IDA_obj))
          cerr << "Unable to get reference to 'One'\n";
      else {
        CORBA::Any FromIDA;                     // To demonstrate we have brand new data
        cout << "Calling GetData\n";
        if (IDA_obj->GetData (FromIDA, 10000)) {
          cout << "Done \n";
          
          TImage *PNewImage;
          cout << "Getting a pointer to the read data\n";
          if (FromIDA >>= PNewImage)
            cout << "Data is: " 
                << "W " << PNewImage->width << " H " << PNewImage->height << " S "
                << PNewImage->imageSize << " WS " <<
                PNewImage->widthStep << " L " << PNewImage->ImagePixels.length() << "\n";
            else
              cerr << "The data we got was of the wrong type\n";
        }
        else
          cerr << "GetData returned false!\n";
      }
    }
    else
      cerr << "Unable to create object 'One'\n";
    
    IDA_obj = GetIDAReference ("One", naming_context);
    if (CORBA::is_nil(IDA_obj))
      cerr << "Could not get reference to 'One'\n";
    else {
      if (IDA_obj->PutData (SentImage, 10000))
        cerr << "Flash IDA should not accept new data\n";
      else
        cout << "Flah IDA correctly did not accept any data\n";
      if (IDA_obj->IsEmpty())
        cerr << "Flash IDA should be full\n";
      else
        cout << "Flash IDA correctly shows not empty (full)\n";
      if (IDA_obj->IsFull())
        cout << "Flash IDA correctly shows as full\n";
      else
        cerr << "Flash IDA should not be not full (empty)\n";
    }
    Factory->RemoveObject ("One");
   
    Factory->CreateObject ("Two", IDApool, SentImage);
    IDA_obj = GetIDAReference ("Two", naming_context);
    if (CORBA::is_nil(IDA_obj))
      cerr << "Could not get a reference to 'Two'\n";
    if (IDA_obj->IsEmpty())
      cout << "Pool correctly shows empty\n";
    else
      cerr << "Pool incorrectly shows not empty\n";
    if (IDA_obj->GetData(ReadImage, 10000))
      cerr << "Should not have received any data from pool\n";
    else
      cout << "Pool correctly timed out on GetData\n";
    if (IDA_obj->PutData(SentImage, 10000))
      cout << "Pool successfully received data\n";
    else
      cerr << "Pool failed to receive data\n";
    if (IDA_obj->PutData(SentImage, 10000))
      cout << "Pool successfully received data again\n";
    else
      cerr << "Pool failed to receive data\n";
    
    Factory->RemoveObject ("Fifty");
    Factory->RemoveObject ("Two");
    
    // This is a simple loop were we show we can create and use objects
    cout << "Ready to receive object names to create/access/remove (one at a time)\n";
    // Send a message to the Factory object.
    do {
      cout << "> ";
      fgets (message, 100, stdin);
      if (strlen(message) <= 1) {
        cerr << "Try a longer string\n";
        continue;
      }
        // Remove the trailing CR
      message[strlen(message)-1] = 0;
      if (Factory->CreateObject (message, IDAchannel, SentImage)) {
          // Object was created, let's see if we can access it through the name service
        IDA_obj = GetIDAReference (message, naming_context);
        if (CORBA::is_nil(IDA_obj)) {
          cerr << "Unable to get the Factory's object reference\n";
          continue;
        }
        try {
          TimeOfDay tod = IDA_obj->get_gmt();
          printf("Current Time: %2.2d:%2.2d:%2.2d\n",tod.hour,tod.minute,tod.second);
          if (IDA_obj->IsEmpty())
            cout << "Comms empty\n";
        }
        catch (const CORBA::Exception &) {
          cerr << "Some error occurred when getting the time\n";
        }
      }
      else
        cout << "Client unable to create a new object '" << message << "\n";
        // Now we remove the object (even if it was not created!)
      Factory->RemoveObject (message);
    } while (strcmp(message, "end"));  // this signals the end of this
    // This shows how all objects can be removed (currently very dangerous!)
    Factory->RemoveAll();
      
    // In this part we show how to pass data around (this is not a very good test because
    // we have here a single thread of execution)
    
    // Start by creating an IDAflash for the image
    if (Factory->CreateObject ("ImageTest", IDAflash, SentImage)) {
      cout << "Image Flash Created\n";
        //Now get a reference to the Image object
      TIDA_var ImageObj = GetIDAReference ("ImageTest", naming_context);
      if (CORBA::is_nil (ImageObj)) 
        cerr << "unable to get reference to Image\n";
      else {
          // Now get data from the object
        cout << "Got reference to Image, saying hello and then getting data\n";
        ImageObj->say_hello("hello world");
        if (!ImageObj->GetData (ReadImage, 1000))
          cout << "GetData failed\n";
        else {
          cout << "We have data\n";
          TImage *PNewImage;                      // a pointer to access the data
          if (ReadImage >>= PNewImage) {
            // NB: Remember that PTImage is only a handle, the data is still owned by ReadImage
            IplImage ImageData;                  // opencv image (Note that this is not a pointer!)
            cout << "Data is: " 
                << "W " << PNewImage->width << " H " << PNewImage->height << " S "
                << PNewImage->imageSize << " WS " <<
                PNewImage->widthStep << " L " << PNewImage->ImagePixels.length() << "\n";
            if (FillOpenCVData (&ImageData, PNewImage)) {
              cvShowImage ("Video", &ImageData);
              cvWaitKey(500);  // this is needed by onpencv to effectively flush
              cout << "Is image displayed? press return "; getchar();
              //cvReleaseImage (&ImageData);
            }
          }
          else
            cout << "Data read was from the incorrect type!\n";
        }
      }
      Factory->RemoveObject("ImageTest");
    }
    else
      cout << "Unable to create object 'ImageTest'\n";
    cout << "we are about to exit, press return "; getchar();
    orb->destroy();
//      delete[] ImageData;
  }
  catch (CORBA::Exception& e) {
    cerr << "CORBA Exception: " << e << endl;
  }
  cout << "Exiting ...\n";
  return EXIT_NORMAL; 
}
